// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: daniel

#pragma once

#include <memory>
#include <utility>

#include "coin2/exchange/base/order/rate_tracker.h"
#include "coin2/exchange/order/connection.pb.h"

constexpr auto EXAMPLE_SPOT_BITFLYER = R"(
"connection_config": {
  "key_filepath": "/home/ec2-user/config/trade/xunke06/bitflyer/trade_key.json",
  "rate_limit_info": {
    "spot_bitflyer": {
      "req_all": {
        "secs": 300,
        "reqs": 500
      },
      "ord_min": {
        "secs": 300,
        "reqs": 300
      }
    }
  }
})";

constexpr auto EXAMPLE_FUTURES_BITFLYER = R"(
"connection_config": {
  "key_filepath": "/home/ec2-user/config/trade/xunke06/bitflyer/trade_key.json",
  "rate_limit_info": {
    "futures_bitflyer": {
      "req_all": {
        "secs": 300,
        "reqs": 500
      },
      "ord_min": {
        "secs": 300,
        "reqs": 300
      }
    }
  }
})";

class BitflyerRateTracker : public coin2::exchange::base::order::IRateTracker {
 public:
  using RateTracker = coin2::exchange::base::order::RateTracker;
  using ActionType = coin2::exchange::base::order::IRateTracker::ActionType;
  using NativeClientConfig = coin2::exchange::order::OrderConnectionConfig;
  using RateLimitReportWriter = coin2::exchange::base::order::RateLimitReportWriter;

 public:
  explicit BitflyerRateTracker(const MarketExchangeApi& mea, const NativeClientConfig& config)
      : ord_rate_tracker_{nullptr}, all_rate_tracker_{nullptr}, rate_limit_report_writer_{nullptr} {
    // rate limit tracker
    using RateConfig = coin2::exchange::base::order::RateConfig;
    if (mea.market == coin::proto::MarketType::Futures) {
      coin2::exchange::order::RateLimitInfoFuturesBitflyer rate_config;
      RateConfig::Get()->GetConfig(mea, config, &rate_config);
      InitRateTracker(&rate_config, mea.String(), EXAMPLE_FUTURES_BITFLYER);
    } else if (mea.market == coin::proto::MarketType::Spot) {
      coin2::exchange::order::RateLimitInfoSpotBitflyer rate_config;
      RateConfig::Get()->GetConfig(mea, config, &rate_config);
      InitRateTracker(&rate_config, mea.String(), EXAMPLE_SPOT_BITFLYER);
    } else {
      CHECK(false) << "unsupported mea: " << mea.String();
    }
  }

  template <class RateConfig>
  void
  InitRateTracker(RateConfig&& rate_config, std::string_view mea_str, std::string_view example) {
    if (!rate_config || !rate_config->has_req_all() || !rate_config->has_ord_min()) {
      coin2::exchange::base::order::RateConfig::CheckFail(mea_str, example);
    }
    const auto& req_all = rate_config->req_all();
    const auto& ord_min = rate_config->ord_min();
    LOG(INFO) << "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
              << "\nOG request rate was set as below:\n"
              << "\nreq_all: [" << req_all.reqs() << " (reqs) / " << req_all.secs() << " (seconds)]"
              << "\nord_min: [" << ord_min.reqs() << " (reqs) / " << ord_min.secs() << " (seconds)]"
              << "\n\nOr you can configure customized rate in driver config. For example: "
              << example
              << "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~";

    ord_rate_tracker_ = std::make_unique<RateTracker>(ord_min.secs() * 1'000'000'000LL);
    ord_rate_tracker_->SetRateLimitInfo(ord_min.reqs(), ord_min.reqs());
    all_rate_tracker_ = std::make_unique<RateTracker>(req_all.secs() * 1'000'000'000LL);
    all_rate_tracker_->SetRateLimitInfo(req_all.reqs(), req_all.reqs());

    // rate limit report
    rate_limit_report_writer_ = std::make_unique<RateLimitReportWriter>();
    rate_limit_report_writer_->SetRateLimitInfoString(rate_config);
  }

  void SetRateLimitInfo(int32_t rate_limit, int32_t rate_limit_remaining) override { CHECK(false); }

  bool HasEnoughRateLimit(int64_t timestamp, ActionType action_type) override {
    bool ret = true;
    if (action_type == ActionType::PLACE || action_type == ActionType::CANCEL) {
      ret = ord_rate_tracker_->HasEnoughRateLimit(timestamp);
    } else {
      ret = all_rate_tracker_->HasEnoughRateLimit(timestamp);
    }
    if (!ret) {
      rate_limit_report_writer_->SetHitRateLimit(timestamp, action_type);
    }
    return ret;
  }

  void Push(int32_t num_reqs, int64_t timestamp, [[maybe_unused]] ActionType action_type) override {
    all_rate_tracker_->Push(num_reqs, timestamp);
    if (action_type == ActionType::PLACE || action_type == ActionType::CANCEL) {
      ord_rate_tracker_->Push(num_reqs, timestamp);
    }
  }

  void ParseRateLimitInfo(
      [[maybe_unused]] const std::shared_ptr<AsioHttpContext>& http_context,
      [[maybe_unused]] ActionType action_type) override {}

  void SetRateLimitInfoString(const google::protobuf::Message* message) override {
    rate_limit_report_writer_->SetRateLimitInfoString(message);
  }

  void WriteRateLimitReportMessage(OrderLoggerCB callback) override {
    if (!callback) {
      return;
    }
    rate_limit_report_writer_->Write(callback);
  }

  double GetMaxRateUsedRatio(int64_t timestamp, ActionType action_type) override {
    switch (action_type) {
      case ActionType::PLACE:
      case ActionType::CANCEL:
      case ActionType::AMEND: {
        if (!ord_rate_tracker_) {
          return 0.;
        }
        auto max_reqs = ord_rate_tracker_->MaxReq();
        auto remaining = ord_rate_tracker_->GetRemaining(timestamp);
        return 1.0 * (max_reqs - remaining) / max_reqs;
      }
      case ActionType::QUERY: {
        if (!all_rate_tracker_) {
          return 0.;
        }
        auto max_reqs = all_rate_tracker_->MaxReq();
        auto remaining = all_rate_tracker_->GetRemaining(timestamp);
        return 1.0 * (max_reqs - remaining) / max_reqs;
      }
      default: {
        // shouldn't reached here
        LOG(ERROR) << "ops~ something is wrong, action_type=" << action_type;
        return 0.;
      }
    }
  }

 private:
  std::unique_ptr<RateTracker> ord_rate_tracker_;
  std::unique_ptr<RateTracker> all_rate_tracker_;
  std::unique_ptr<RateLimitReportWriter> rate_limit_report_writer_;
};
